﻿using System;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Linq;

namespace OpenWaves
{
    public struct BinaryUnit : IEquatable<BinaryUnit>, IComparable<BinaryUnit>
    {
        public static BinaryUnit FromBytes(long value)
        {
            return new BinaryUnit(GetBytes(value, BinaryUnitType.Byte));
        }

        public static BinaryUnit FromKilobytes(double value)
        {
            return new BinaryUnit(GetBytes(value, BinaryUnitType.Kilobyte));
        }

        private static int GetBytes(double value, BinaryUnitType unitType)
        {
            return (int)(value * Math.Pow(2, (long)unitType * 10));
        }

        public static BinaryUnit FromMegabytes(double value)
        {
            return new BinaryUnit(GetBytes(value, BinaryUnitType.Megabyte));
        }

        public static BinaryUnit FromGigabytes(double value)
        {
            return new BinaryUnit(GetBytes(value, BinaryUnitType.Gigabyte));
        }

        private readonly long bytes;

        public double Kilobytes
        {
            get { return this.bytes / 1024.0; }
        }

        public double Megabytes
        {
            get { return this.Kilobytes / 1024; }
        }

        public long Bytes
        {
            get { return bytes; }
        }

        private BinaryUnit(long bytes)
        {
            this.bytes = bytes;
        }

        public static implicit operator long(BinaryUnit unit)
        {
            return unit.bytes;
        }

        public int CompareTo(BinaryUnit other)
        {
            return this.Bytes.CompareTo(other.Bytes);
        }

        public override string ToString()
        {
            return this.ToString(0);
        }

        public string ToString(string format)
        {
            return this.ToString(format, CultureInfo.CurrentCulture);
        }

        public string ToString(int digits)
        {
            Contract.Requires<ArgumentException>(digits >= 0);

            if (digits == 0)
            {
                return this.ToString("0", CultureInfo.CurrentCulture);
            }

            return this.ToString("0." + Enumerable.Repeat("0", digits).JoinStrings(), CultureInfo.CurrentCulture);
        }

        public string ToString(string format, IFormatProvider provider)
        {
            const double divider = 1024.0;

            double value = this.bytes;
            int power = 0;

            //has non-fractional significant value 
            while (value / divider > 1)
            {
                value = value / divider;
                power++;
            }

            string formated = value.ToString(format, provider);
            string unitName = ((BinaryUnitType)power).GetDisplayText();

            return string.Format(CultureInfo.CurrentCulture, "{0} {1}", formated, unitName);
        }

        public BinaryUnit Add(BinaryUnit unit)
        {
            return this + unit;
        }

        public bool Equals(BinaryUnit other)
        {
            return other.bytes == this.bytes;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (obj.GetType() != typeof(BinaryUnit)) return false;
            return Equals((BinaryUnit)obj);
        }

        public override int GetHashCode()
        {
            return this.bytes.GetHashCode();
        }

        public static bool operator ==(BinaryUnit left, BinaryUnit right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(BinaryUnit left, BinaryUnit right)
        {
            return !left.Equals(right);
        }

        public static BinaryUnit operator +(BinaryUnit left, BinaryUnit right)
        {
            return new BinaryUnit(left.bytes + right.bytes);
        }

        public static bool operator <(BinaryUnit left, BinaryUnit right)
        {
            return left.bytes < right.bytes;
        }

        public static bool operator >(BinaryUnit left, BinaryUnit right)
        {
            return left.bytes > right.bytes;
        }

        public static bool operator <=(BinaryUnit left, BinaryUnit right)
        {
            return left.bytes <= right.bytes;
        }

        public static bool operator >=(BinaryUnit left, BinaryUnit right)
        {
            return left.bytes >= right.bytes;
        }
    }
}